package czopyk.model.structure;

import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.apache.log4j.Logger;

import czopyk.model.factories.RequestFactory;
import czopyk.model.factories.Utils;
import czopyk.model.request.LoadClass;
import czopyk.model.request.Request;
import cern.jet.random.Empirical;
import cern.jet.random.EmpiricalWalker;

public class LoadSelector {

	protected Logger log = Logger.getLogger("Load selector");

	// prawdopodobienstwa klas
	private final Map<LoadClass, BigDecimal> loadClassToProbab = new HashMap<LoadClass, BigDecimal>();
	private final Map<Integer, LoadClass> indexToLoadClass = new HashMap<Integer, LoadClass>();
	private EmpiricalWalker ew;
	private boolean ready = false;

	private RequestFactory rf;

	public Set<LoadClass> getLoad() {
		Set<LoadClass> set = loadClassToProbab.keySet();
		Iterator<LoadClass> i = set.iterator();
		while (i.hasNext()) {
			if (loadClassToProbab.get(i.next()).equals(BigDecimal.ZERO)) {
				i.remove();
			}
		}
		return set;
	}

	public boolean isSelectorReady() {
		return ready;
	}

	public void registerClass(LoadClass lc, BigDecimal probab) {
		
		if (lc == null) {
			throw new IllegalArgumentException("Cannot register null loadClass");
		}
		
		if (probab.compareTo(BigDecimal.ZERO) < 0
				|| probab.compareTo(BigDecimal.ONE) > 1) {
			throw new IllegalArgumentException("Probability wrong value: "
					+ probab.doubleValue());
		}

		if (probab.compareTo(BigDecimal.ZERO) == 0) {
			log.warn("Probability value = 0.0 for load class: " + lc
					+ " - excluding from overall load.");
			return;
		}

		loadClassToProbab.put(lc, probab);

		BigDecimal s = BigDecimal.ZERO;
		for (BigDecimal d : loadClassToProbab.values()) {
			s = s.add(d);
		}

		if (s.compareTo(BigDecimal.ONE) == 0) {
			ready = true;
			buildDistribution();
		} else {
			ready = false;
		}

	}

	private void buildDistribution() {
		log.debug("Building distribution...");
		double p;
		double[] cdf = new double[loadClassToProbab.size()];
		int i = -1;
		indexToLoadClass.clear();
		for (LoadClass l : loadClassToProbab.keySet()) {
			p = loadClassToProbab.get(l).doubleValue();
			indexToLoadClass.put(++i, l);
			cdf[i] = p;/*(i > 0 ? cdf[i - 1] + p : p);*/
			log.debug("cdf[" + indexToLoadClass.get(i) + "] = " + cdf[i]);
		}
		ew = new EmpiricalWalker(cdf, Empirical.NO_INTERPOLATION,
				Utils.getNewRandomEngine());
	}

	public Request getRequest() {
		if (!ready) {
			throw new IllegalStateException("");
		}
		Request r = rf.getRequest(indexToLoadClass.get(ew.nextInt()));
		if (r == null) {
			throw new RuntimeException("Random generation of request failed.");
		}
		return r;
	}

	public void setRf(RequestFactory rf) {
		this.rf = rf;
	}

	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder("LoadSelector [");
		for (LoadClass l : loadClassToProbab.keySet()) {
			sb.append(l + " -> " + loadClassToProbab.get(l) + "\n");
		}
		sb.append("ready: " + ready + "]");
		return sb.toString();
	}

}
